 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.update.internal.operations;

 //import java.io.*;
 //import java.net.*;
 //import java.nio.channels.*;
 import java.io.File ;
 import java.io.IOException ;
 import java.net.URL ;
 import java.net.URLConnection ;
 import java.util.ArrayList ;
 import java.util.Collections ;
 import java.util.HashMap ;
 import java.util.HashSet ;
 import java.util.Set ;
 import java.util.StringTokenizer ;

 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProduct;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.MultiStatus;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.PluginVersionIdentifier;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.update.configuration.IConfiguredSite;
 import org.eclipse.update.configuration.IInstallConfiguration;
 import org.eclipse.update.configuration.ILocalSite;
 import org.eclipse.update.configurator.ConfiguratorUtils;
 import org.eclipse.update.configurator.IPlatformConfiguration;
 import org.eclipse.update.core.IFeature;
 import org.eclipse.update.core.IFeatureReference;
 import org.eclipse.update.core.IImport;
 import org.eclipse.update.core.IIncludedFeatureReference;
 import org.eclipse.update.core.IPluginEntry;
 import org.eclipse.update.core.ISiteFeatureReference;
 import org.eclipse.update.core.IURLEntry;
 import org.eclipse.update.core.SiteManager;
 import org.eclipse.update.core.VersionedIdentifier;
 import org.eclipse.update.internal.configurator.PlatformConfiguration;
 import org.eclipse.update.internal.core.Messages;
 import org.eclipse.update.internal.core.UpdateCore;
 import org.eclipse.update.operations.IInstallFeatureOperation;
 import org.eclipse.update.operations.IOperationValidator;
 import org.osgi.framework.Bundle;

 /**
  *
  */
 public class OperationValidator implements IOperationValidator {
     /**
      * Checks if the platform configuration has been modified outside this program.
      * @return the error status, or null if no errors
      */
     public IStatus validatePlatformConfigValid() {
         ArrayList status = new ArrayList (1);
         checkPlatformWasModified(status);
         
         // report status
 if (status.size() > 0)
             return createMultiStatus(Messages.ActivityConstraints_rootMessage, status, IStatus.ERROR);
         return null;
     }
     
     /*
      * Called by UI before performing operation. Returns null if no errors, a
      * status with IStatus.WARNING code when the initial configuration is
      * broken, or a status with IStatus.ERROR when there the operation
      * introduces new errors
      */
     public IStatus validatePendingInstall(
         IFeature oldFeature,
         IFeature newFeature) {
         // check initial state
 ArrayList beforeStatus = new ArrayList ();
         validateInitialState(beforeStatus);

         // check proposed change
 ArrayList status = new ArrayList ();
         checkPlatformWasModified(status);
         validateInstall(oldFeature, newFeature, status);

         // report status
 return createCombinedReportStatus(beforeStatus, status);
     }

     /*
      * Called by UI before performing operation
      */
     public IStatus validatePendingUnconfig(IFeature feature) {
         // check initial state
 ArrayList beforeStatus = new ArrayList ();
         validateInitialState(beforeStatus);

         // check proposed change
 ArrayList status = new ArrayList ();
         checkPlatformWasModified(status);
         validateUnconfigure(feature, status);

         // report status
 return createCombinedReportStatus(beforeStatus, status);
     }

     /*
      * Called by UI before performing operation
      */
     public IStatus validatePendingConfig(IFeature feature) {
         // check initial state
 ArrayList beforeStatus = new ArrayList ();
         validateInitialState(beforeStatus);

         // check proposed change
 ArrayList status = new ArrayList ();
         checkPlatformWasModified(status);
         validateConfigure(feature, status);

         // report status
 return createCombinedReportStatus(beforeStatus, status);
     }

     /**
      * Called before performing operation.
      */
     public IStatus validatePendingReplaceVersion(
         IFeature feature,
         IFeature anotherFeature) {
         // check initial state
 ArrayList beforeStatus = new ArrayList ();
         validateInitialState(beforeStatus);

         // check proposed change
 ArrayList status = new ArrayList ();
         checkPlatformWasModified(status);
         validateReplaceVersion(feature, anotherFeature, status);

         // report status
 return createCombinedReportStatus(beforeStatus, status);
     }


     /*
      * Called by the UI before doing a revert/ restore operation
      */
     public IStatus validatePendingRevert(IInstallConfiguration config) {
         // check initial state
 ArrayList beforeStatus = new ArrayList ();
         validateInitialState(beforeStatus);

         // check proposed change
 ArrayList status = new ArrayList ();
         checkPlatformWasModified(status);
         validateRevert(config, status);

         // report status
 return createCombinedReportStatus(beforeStatus, status);
     }

     /*
      * Called by the UI before doing a batched processing of several pending
      * changes.
      */
     public IStatus validatePendingChanges(IInstallFeatureOperation[] jobs) {
         // check initial state
 ArrayList beforeStatus = new ArrayList ();
         validateInitialState(beforeStatus);
         checkPlatformWasModified(beforeStatus);

         // check proposed change
 ArrayList status = new ArrayList ();
         validatePendingChanges(jobs, status, beforeStatus);

         // report status
 return createCombinedReportStatus(beforeStatus, status);
     }
     
     /*
      * Called by the UI before doing a batched processing of several pending
      * changes.
      */
     public RequiredFeaturesResult getRequiredFeatures(IInstallFeatureOperation[] jobs) {
         
         RequiredFeaturesResult requiredFeaturesResult = new RequiredFeaturesResult();
         // check initial state
 ArrayList beforeStatus = new ArrayList ();
         validateInitialState(beforeStatus);
         checkPlatformWasModified(beforeStatus);

         // check proposed change
 ArrayList status = new ArrayList ();
         Set requiredFeatures = validatePendingChanges(jobs, status, beforeStatus);

         // report status
 //return createCombinedReportStatus(beforeStatus, status);
 requiredFeaturesResult.setRequiredFeatures(requiredFeatures);
         requiredFeaturesResult.setStatus(createCombinedReportStatus(beforeStatus, status));
         return requiredFeaturesResult;
     }

     /*
      * Check the current state.
      */
     public IStatus validateCurrentState() {
         // check the state
 ArrayList status = new ArrayList ();
         checkPlatformWasModified(status);
         validateInitialState(status);

         // report status
 if (status.size() > 0)
             return createMultiStatus(Messages.ActivityConstraints_rootMessage, status, IStatus.ERROR);
         return null;
     }

     /*
      * Check to see if we are not broken even before we start
      */
     private static void validateInitialState(ArrayList status) {
         try {
             ArrayList features = computeFeatures();
             // uncomment this when patch released in boot
 //checkConfigurationLock(status);
 checkConstraints(features, status);
         } catch (CoreException e) {
             status.add(e.getStatus());
         }
     }

     /*
      * handle unconfigure
      */
     private static void validateUnconfigure(
         IFeature feature,
         ArrayList status) {
         try {
             checkSiteReadOnly(feature,status);
             ArrayList features = computeFeatures();
             features = computeFeaturesAfterOperation(features, null, feature);
             checkConstraints(features, status);
         } catch (CoreException e) {
             status.add(e.getStatus());
         }
     }


     /*
      * handle configure
      */
     private static void validateConfigure(IFeature feature, ArrayList status) {
         try {
             checkSiteReadOnly(feature,status);
             ArrayList features = computeFeatures();
             checkOptionalChildConfiguring(feature, status);
             checkForCycles(feature, null, features);
             features = computeFeaturesAfterOperation(features, feature, null);
             checkConstraints(features, status);

         } catch (CoreException e) {
             status.add(e.getStatus());
         }
     }

     /*
      * handle replace version
      */
     private static void validateReplaceVersion(
         IFeature feature,
         IFeature anotherFeature,
         ArrayList status) {
         try {
             checkSiteReadOnly(feature,status);
             ArrayList features = computeFeatures();
             checkForCycles(feature, null, features);
             features =
                 computeFeaturesAfterOperation(
                     features,
                     anotherFeature,
                     feature);
             checkConstraints(features, status);
         } catch (CoreException e) {
             status.add(e.getStatus());
         }
     }

     /*
      * handle install and update
      */
     private static void validateInstall(
         IFeature oldFeature,
         IFeature newFeature,
         ArrayList status) {
         try {
             checkSiteReadOnly(oldFeature,status);
             ArrayList features = computeFeatures();
             checkForCycles(newFeature, null, features);
             features =
                 computeFeaturesAfterOperation(features, newFeature, oldFeature);
             checkConstraints(features, status);
             checkLicense(newFeature, status);
         } catch (CoreException e) {
             status.add(e.getStatus());
         }
     }

     /*
      * handle revert and restore
      */
     private static void validateRevert(
         IInstallConfiguration config,
         ArrayList status) {
         try {
 // // check the timeline and don't bother
 // // to check anything else if negative
 // if (!checkTimeline(config, status))
 // return;
 ArrayList features = computeFeaturesAfterRevert(config);
             checkConstraints(features, status);
             checkRevertConstraints(features, status);

         } catch (CoreException e) {
             status.add(e.getStatus());
         }
     }


     /*
      * Handle one-click changes as a batch
      */
     private static Set validatePendingChanges(
         IInstallFeatureOperation[] jobs,
         ArrayList status,
         ArrayList beforeStatus) {
         try {
             ArrayList features = computeFeatures();
             ArrayList savedFeatures = features;
             int nexclusives = 0;

             // pass 1: see if we can process the entire "batch"
 ArrayList tmpStatus = new ArrayList ();
             for (int i = 0; i < jobs.length; i++) {
                 IInstallFeatureOperation job = jobs[i];

                 IFeature newFeature = job.getFeature();
                 IFeature oldFeature = job.getOldFeature();
                 checkLicense(newFeature, status);
                 if (jobs.length > 1 && newFeature.isExclusive()) {
                     nexclusives++;
                     status.add(
                         createStatus(
                             newFeature,
                             FeatureStatus.CODE_EXCLUSIVE,
                             Messages.ActivityConstraints_exclusive));
                     continue;
                 }
                 checkForCycles(newFeature, null, features);
                 features =
                     computeFeaturesAfterOperation(
                         features,
                         newFeature,
                         oldFeature);
             }
             if (nexclusives > 0)
                 return Collections.EMPTY_SET;
             checkConstraints(features, tmpStatus);
             if (tmpStatus.size() == 0) // the whole "batch" is OK
 return Collections.EMPTY_SET;

             // pass 2: we have conflicts
 features = savedFeatures;
             for (int i = 0; i < jobs.length; i++) {
                 IInstallFeatureOperation job = jobs[i];
                 IFeature newFeature = job.getFeature();
                 IFeature oldFeature = job.getOldFeature();

                 features =
                     computeFeaturesAfterOperation(
                         features,
                         newFeature,
                         oldFeature);

                 Set result = checkConstraints(features, status);
                 if (status.size() > 0
                     && !isBetterStatus(beforeStatus, status)) {
 // bug 75613
 // IStatus conflict =
 // createStatus(
 // newFeature,
 // FeatureStatus.CODE_OTHER,
 // Policy.bind(KEY_CONFLICT));
 // status.add(0, conflict);
 return result;
                 }
             }
         } catch (CoreException e) {
             status.add(e.getStatus());
         }
         
         return Collections.EMPTY_SET;
     }
     
     private static void checkPlatformWasModified(ArrayList status) {
         try {
             // checks if the platform has been modified outside this eclipse instance
 IPlatformConfiguration platformConfig = ConfiguratorUtils.getCurrentPlatformConfiguration();
             
             long currentTimeStamp = platformConfig.getChangeStamp();
             // get the last modified value for this config, from this process point of view
 if (platformConfig instanceof PlatformConfiguration)
                 currentTimeStamp = ((PlatformConfiguration)platformConfig).getConfiguration().lastModified();
                 
             // get the real last modified value
 URL platformXML = platformConfig.getConfigurationLocation();
             long actualTimeStamp = currentTimeStamp;
             if ("file".equals(platformXML.getProtocol())) //$NON-NLS-1$
 actualTimeStamp = new File (platformXML.getFile()).lastModified();
             else {
                 URLConnection connection = platformXML.openConnection();
                 actualTimeStamp = connection.getLastModified();
             }
             if (currentTimeStamp != actualTimeStamp)
                 status.add(createStatus(
                                 null,
                                 FeatureStatus.CODE_OTHER,
                                 Messages.ActivityConstraints_platformModified));
         } catch (IOException e) {
             // ignore
 }
     }
     
     private static void checkSiteReadOnly(IFeature feature, ArrayList status) {
         if(feature == null){
             return;
         }
         IConfiguredSite csite = feature.getSite().getCurrentConfiguredSite();
         if (csite != null && !csite.isUpdatable())
             status.add(createStatus(feature, FeatureStatus.CODE_OTHER,
                     NLS.bind(Messages.ActivityConstraints_readOnly, (new String [] { csite.getSite().getURL().toExternalForm() }))));
     }

     /*
      * Compute a list of configured features
      */
     private static ArrayList computeFeatures() throws CoreException {
         return computeFeatures(true);
     }
     /*
      * Compute a list of configured features
      */
     private static ArrayList computeFeatures(boolean configuredOnly)
         throws CoreException {
         ArrayList features = new ArrayList ();
         ILocalSite localSite = SiteManager.getLocalSite();
         IInstallConfiguration config = localSite.getCurrentConfiguration();
         IConfiguredSite[] csites = config.getConfiguredSites();

         for (int i = 0; i < csites.length; i++) {
             IConfiguredSite csite = csites[i];

             IFeatureReference[] crefs;

             if (configuredOnly)
                 crefs = csite.getConfiguredFeatures();
             else
                 crefs = csite.getSite().getFeatureReferences();
             for (int j = 0; j < crefs.length; j++) {
                 IFeatureReference cref = crefs[j];
                 IFeature cfeature = cref.getFeature(null);
                 features.add(cfeature);
             }
         }

         return features;
     }

     /*
      * Compute the nested feature subtree starting at the specified base
      * feature
      */
     public static ArrayList computeFeatureSubtree(
             IFeature top,
             IFeature feature,
             ArrayList features,
             boolean tolerateMissingChildren,
             ArrayList configuredFeatures,
             ArrayList visitedFeatures)
     throws CoreException {

         // check arguments
 if (top == null)
             return features;
         if (feature == null)
             feature = top;
         if (features == null)
             features = new ArrayList ();
         if (visitedFeatures == null)
             visitedFeatures = new ArrayList ();

         // check for <includes> cycle
 if (visitedFeatures.contains(feature)) {
             IStatus status =
             createStatus(top, FeatureStatus.CODE_CYCLE, Messages.ActivityConstraints_cycle);
             throw new CoreException(status);
         } else {
             // keep track of visited features so we can detect cycles
 visitedFeatures.add(feature);
         }

         // return specified base feature and all its children
 if (!features.contains(feature))
             features.add(feature);
         IIncludedFeatureReference[] children =
         feature.getIncludedFeatureReferences();
         for (int i = 0; i < children.length; i++) {
             try {
                 IFeature child = UpdateUtils.getIncludedFeature(feature, children[i]);
                 features =
                 computeFeatureSubtree(
                         top,
                         child,
                         features,
                         tolerateMissingChildren,
                         null,
                         visitedFeatures);
             } catch (CoreException e) {
                 if (!children[i].isOptional() && !tolerateMissingChildren)
                     throw e;
             }
         }
         // no cycles for this feature during DFS
 visitedFeatures.remove(feature);
         return features;
     }

     private static void checkLicense(IFeature feature, ArrayList status) {
         IURLEntry licenseEntry = feature.getLicense();
         if (licenseEntry != null) {
             String license = licenseEntry.getAnnotation();
             if (license != null && license.trim().length() > 0)
                 return;
         }
         status.add(
             createStatus(feature, FeatureStatus.CODE_OTHER, Messages.ActivityConstraints_noLicense));
     }

     /*
      * Compute a list of features that will be configured after the operation
      */
     private static ArrayList computeFeaturesAfterOperation(
         ArrayList features,
         IFeature add,
         IFeature remove)
         throws CoreException {

         ArrayList addTree = computeFeatureSubtree(add, null, null, false,
         /* do not tolerate missing children */
         features, null);
         ArrayList removeTree =
             computeFeatureSubtree(
                 remove,
                 null,
                 null,
                 true /* tolerate missing children */,
                 null,
                 null
         );
         if (remove != null) {
             // Patches to features are removed together with
 // those features. Include them in the list.
 contributePatchesFor(removeTree, features, removeTree);
         }

         if (remove != null)
             features.removeAll(removeTree);

         if (add != null)
             features.addAll(addTree);

         return features;
     }

     private static void contributePatchesFor(
         ArrayList removeTree,
         ArrayList features,
         ArrayList result)
         throws CoreException {

         for (int i = 0; i < removeTree.size(); i++) {
             IFeature feature = (IFeature) removeTree.get(i);
             contributePatchesFor(feature, features, result);
         }
     }

     private static void contributePatchesFor(
         IFeature feature,
         ArrayList features,
         ArrayList result)
         throws CoreException {
         for (int i = 0; i < features.size(); i++) {
             IFeature candidate = (IFeature) features.get(i);
             if (UpdateUtils.isPatch(feature, candidate)) {
                 ArrayList removeTree =
                     computeFeatureSubtree(candidate, null, null, true,null,null);
                 result.addAll(removeTree);
             }
         }
     }

     /*
      * Compute a list of features that will be configured after performing the
      * revert
      */
     private static ArrayList computeFeaturesAfterRevert(IInstallConfiguration config)
         throws CoreException {

         ArrayList list = new ArrayList ();
         IConfiguredSite[] csites = config.getConfiguredSites();
         for (int i = 0; i < csites.length; i++) {
             IConfiguredSite csite = csites[i];
             IFeatureReference[] features = csite.getConfiguredFeatures();
             for (int j = 0; j < features.length; j++) {
                 list.add(features[j].getFeature(null));
             }
         }
         return list;
     }



     /*
      * Compute a list of plugin entries for the specified features.
      */
     private static ArrayList computePluginsForFeatures(ArrayList features)
         throws CoreException {
         if (features == null)
             return new ArrayList ();

         HashMap plugins = new HashMap ();
         for (int i = 0; i < features.size(); i++) {
             IFeature feature = (IFeature) features.get(i);
             IPluginEntry[] entries = feature.getPluginEntries();
             for (int j = 0; j < entries.length; j++) {
                 IPluginEntry entry = entries[j];
                 plugins.put(entry.getVersionedIdentifier(), entry);
             }
         }
         ArrayList result = new ArrayList ();
         result.addAll(plugins.values());
         return result;
     }


     /**
      * Check for feature cycles:
      * - visit feature
      * - if feature is in the cycle candidates list, then cycle found, else add it to candidates list
      * - DFS children
      * - when return from DFS remove the feature from the candidates list
      */
     private static void checkForCycles(
             IFeature feature,
             ArrayList candidates,
             ArrayList configuredFeatures)
     throws CoreException {

         // check arguments
 if (feature == null)
             return;
         if (configuredFeatures == null)
             configuredFeatures = new ArrayList ();
         if (candidates == null)
             candidates = new ArrayList ();
         
         // check for <includes> cycle
 if (candidates.contains(feature)) {
             String msg = NLS.bind(Messages.ActivityConstraints_cycle, (new String [] {feature.getLabel(),
             feature.getVersionedIdentifier().toString()}));
             IStatus status = createStatus(feature, FeatureStatus.CODE_CYCLE, msg);
             throw new CoreException(status);
         }

         // potential candidate
 candidates.add(feature);
         
         // recursively, check cycles with children
 IIncludedFeatureReference[] children =
         feature.getIncludedFeatureReferences();
         for (int i = 0; i < children.length; i++) {
             try {
                 IFeature child = UpdateUtils.getIncludedFeature(feature, children[i]);
                 checkForCycles(child, candidates, configuredFeatures);
             } catch (CoreException e) {
                 if (!children[i].isOptional())
                     throw e;
             }
         }
         // no longer a candidate, because no cycles with children
 candidates.remove(feature);
     }
     
     /*
      * validate constraints
      */
     private static Set checkConstraints(ArrayList features, ArrayList status)
         throws CoreException {
         if (features == null)
             return Collections.EMPTY_SET;

         ArrayList plugins = computePluginsForFeatures(features);

         checkEnvironment(features, status);
         checkPlatformFeature(features, plugins, status);
         checkPrimaryFeature(features, plugins, status);
         return checkPrereqs(features, plugins, status);
     }

     /*
      * Verify all features are either portable, or match the current
      * environment
      */
     private static void checkEnvironment(
         ArrayList features,
         ArrayList status) {

         String os = Platform.getOS();
         String ws = Platform.getWS();
         String arch = Platform.getOSArch();

         for (int i = 0; i < features.size(); i++) {
             IFeature feature = (IFeature) features.get(i);
             ArrayList fos = createList(feature.getOS());
             ArrayList fws = createList(feature.getWS());
             ArrayList farch = createList(feature.getOSArch());

             if (fos.size() > 0) {
                 if (!fos.contains(os)) {
                     IStatus s =
                         createStatus(feature, FeatureStatus.CODE_ENVIRONMENT, Messages.ActivityConstraints_os);
                     if (!status.contains(s))
                         status.add(s);
                     continue;
                 }
             }

             if (fws.size() > 0) {
                 if (!fws.contains(ws)) {
                     IStatus s =
                         createStatus(feature, FeatureStatus.CODE_ENVIRONMENT, Messages.ActivityConstraints_ws);
                     if (!status.contains(s))
                         status.add(s);
                     continue;
                 }
             }

             if (farch.size() > 0) {
                 if (!farch.contains(arch)) {
                     IStatus s =
                         createStatus(feature, FeatureStatus.CODE_ENVIRONMENT, Messages.ActivityConstraints_arch);
                     if (!status.contains(s))
                         status.add(s);
                     continue;
                 }
             }
         }
     }

     /*
      * Verify we end up with a version of platform configured
      */
     private static void checkPlatformFeature(
         ArrayList features,
         ArrayList plugins,
         ArrayList status) {

         // find the plugin that defines the product
 IProduct product = Platform.getProduct();
         if (product == null)
             return; // normally this shouldn't happen
 Bundle primaryBundle = product.getDefiningBundle();
         // check if that plugin is among the resulting plugins
 boolean found = false;
         for (int j = 0; j < plugins.size(); j++) {
             IPluginEntry plugin = (IPluginEntry) plugins.get(j);
             if (primaryBundle.getSymbolicName().equals(plugin.getVersionedIdentifier().getIdentifier())) {
                 found = true;
                 break;
             }
         }
         
         if (!found) {
             IStatus s =
                 createStatus(null, FeatureStatus.CODE_OTHER, Messages.ActivityConstraints_platform);
             if (!status.contains(s))
                 status.add(s);
         }
     }

     /*
      * Verify we end up with a version of primary feature configured
      */
     private static void checkPrimaryFeature(
         ArrayList features,
         ArrayList plugins,
         ArrayList status) {

         String featureId =
             ConfiguratorUtils
                 .getCurrentPlatformConfiguration()
                 .getPrimaryFeatureIdentifier();
         
         if (featureId != null) {
             // primary feature is defined
 for (int i = 0; i < features.size(); i++) {
                 IFeature feature = (IFeature) features.get(i);
                 if (featureId
                     .equals(feature.getVersionedIdentifier().getIdentifier()))
                     return;
             }
     
             IStatus s = createStatus(null, FeatureStatus.CODE_OTHER, Messages.ActivityConstraints_primary);
             if (!status.contains(s))
                 status.add(s);
         } else {
             // check if the product still ends up contributed
 // find the plugin that defines the product
 IProduct product = Platform.getProduct();
             if (product == null)
                 return; // normally this shouldn't happen
 Bundle primaryBundle = product.getDefiningBundle();
             // check if that plugin is among the resulting plugins

             for (int j = 0; j < plugins.size(); j++) {
                 IPluginEntry plugin = (IPluginEntry) plugins.get(j);
                 if (primaryBundle.getSymbolicName().equals(plugin.getVersionedIdentifier().getIdentifier())) {
                     return; // product found
 }
             }
             IStatus s =
                 createStatus(null, FeatureStatus.CODE_OTHER, Messages.ActivityConstraints_primary);
             if (!status.contains(s))
                 status.add(s);
         }
     }

     /*
      * Verify we do not break prereqs
      */
     private static Set checkPrereqs(
         ArrayList features,
         ArrayList plugins,
         ArrayList status) {
         
         HashSet result = new HashSet ();

         for (int i = 0; i < features.size(); i++) {
             IFeature feature = (IFeature) features.get(i);
             IImport[] imports = feature.getImports();

             for (int j = 0; j < imports.length; j++) {
                 IImport iimport = imports[j];
                 // for each import determine plugin or feature, version, match
 // we need
 VersionedIdentifier iid = iimport.getVersionedIdentifier();
                 String id = iid.getIdentifier();
                 PluginVersionIdentifier version = iid.getVersion();
                 boolean featurePrereq =
                     iimport.getKind() == IImport.KIND_FEATURE;
                 boolean ignoreVersion =
                     version.getMajorComponent() == 0
                         && version.getMinorComponent() == 0
                         && version.getServiceComponent() == 0;
                 int rule = iimport.getRule();
                 if (rule == IImport.RULE_NONE)
                     rule = IImport.RULE_COMPATIBLE;

                 boolean found = false;

                 ArrayList candidates;

                 if (featurePrereq)
                     candidates = features;
                 else
                     candidates = plugins;
                 for (int k = 0; k < candidates.size(); k++) {
                     VersionedIdentifier cid;
                     if (featurePrereq) {
                         // the candidate is a feature
 IFeature candidate = (IFeature) candidates.get(k);
                         // skip self
 if (feature.equals(candidate))
                             continue;
                         cid = candidate.getVersionedIdentifier();
                     } else {
                         // the candidate is a plug-in
 IPluginEntry plugin = (IPluginEntry) candidates.get(k);
                         cid = plugin.getVersionedIdentifier();
                     }
                     PluginVersionIdentifier cversion = cid.getVersion();
                     if (id.equals(cid.getIdentifier())) {
                         // have a candidate
 if (ignoreVersion)
                             found = true;
                         else if (
                             rule == IImport.RULE_PERFECT
                                 && cversion.isPerfect(version))
                             found = true;
                         else if (
                             rule == IImport.RULE_EQUIVALENT
                                 && cversion.isEquivalentTo(version))
                             found = true;
                         else if (
                             rule == IImport.RULE_COMPATIBLE
                                 && cversion.isCompatibleWith(version))
                             found = true;
                         else if (
                             rule == IImport.RULE_GREATER_OR_EQUAL
                                 && cversion.isGreaterOrEqualTo(version))
                             found = true;
                     }
                     if (found)
                         break;
                 }

                 if (!found) {
                     // report status
 String target =
                         featurePrereq
                             ? Messages.ActivityConstaints_prereq_feature
                             : Messages.ActivityConstaints_prereq_plugin;
                     int errorCode = featurePrereq
                             ? FeatureStatus.CODE_PREREQ_FEATURE
                             : FeatureStatus.CODE_PREREQ_PLUGIN;
                     String msg =
                         NLS.bind(Messages.ActivityConstraints_prereq, (new String [] { target, id }));

                     if (!ignoreVersion) {
                         if (rule == IImport.RULE_PERFECT)
                             msg =
                                 NLS.bind(Messages.ActivityConstraints_prereqPerfect, (new String [] {
                                 target,
                                 id,
                                 version.toString()}));
                         else if (rule == IImport.RULE_EQUIVALENT)
                             msg =
                                 NLS.bind(Messages.ActivityConstraints_prereqEquivalent, (new String [] {
                                 target,
                                 id,
                                 version.toString()}));
                         else if (rule == IImport.RULE_COMPATIBLE)
                             msg =
                                 NLS.bind(Messages.ActivityConstraints_prereqCompatible, (new String [] {
                                 target,
                                 id,
                                 version.toString()}));
                         else if (rule == IImport.RULE_GREATER_OR_EQUAL)
                             msg =
                                 NLS.bind(Messages.ActivityConstraints_prereqGreaterOrEqual, (new String [] {
                                 target,
                                 id,
                                 version.toString()}));
                     }
                     IStatus s = createStatus(feature, errorCode, msg);
                     result.add(new InternalImport(iimport));
                     if (!status.contains(s))
                         status.add(s);
                 }
             }
         }
         
         return result;
     }

     /*
      * Verify we end up with valid nested features after revert
      */
     private static void checkRevertConstraints(
         ArrayList features,
         ArrayList status) {

         for (int i = 0; i < features.size(); i++) {
             IFeature feature = (IFeature) features.get(i);
             try {
                 computeFeatureSubtree(
                     feature,
                     null,
                     null,
                     false /* do not tolerate missing children */,
                     null,
                     null
                 );
             } catch (CoreException e) {
                 status.add(e.getStatus());
             }
        }
    }

    /*
     * Verify that a parent of an optional child is configured before we allow
     * the child to be configured as well
     */

    private static void checkOptionalChildConfiguring(
        IFeature feature,
        ArrayList status)
        throws CoreException {
        ILocalSite localSite = SiteManager.getLocalSite();
        IInstallConfiguration config = localSite.getCurrentConfiguration();
        IConfiguredSite[] csites = config.getConfiguredSites();

        boolean included = false;
        for (int i = 0; i < csites.length; i++) {
            IConfiguredSite csite = csites[i];
            ISiteFeatureReference[] crefs =
                csite.getSite().getFeatureReferences();
            for (int j = 0; j < crefs.length; j++) {
                IFeatureReference cref = crefs[j];
                IFeature cfeature = null;
                try {
                    cfeature = cref.getFeature(null);
                } catch (CoreException e) {
                    //FIXME: cannot ask 'isOptional' here
 // Ignore missing optional feature.
 /*
                     * if (cref.isOptional()) continue;
                     */
                    throw e;
                }
                if (isParent(cfeature, feature, true)) {
                    // Included in at least one feature as optional
 included = true;
                    if (csite.isConfigured(cfeature)) {
                        // At least one feature parent
 // is enabled - it is OK to
 // configure optional child.
 return;
                    }
                }
            }
        }
        if (included) {
            // feature is included as optional but
 // no parent is currently configured.
 String msg = Messages.ActivityConstraints_optionalChild;
            status.add(createStatus(feature, FeatureStatus.CODE_OPTIONAL_CHILD, msg));
        } else {
            //feature is root - can be configured
 }
    }
//
// /**
// * Checks if the configuration is locked by other instances
// *
// * @param status
// */
// private static void checkConfigurationLock(ArrayList status) {
// IPlatformConfiguration config =
// BootLoader.getCurrentPlatformConfiguration();
// URL configURL = config.getConfigurationLocation();
// if (!"file".equals(configURL.getProtocol())) {
// status.add(
// createStatus(
// null,
// "Configuration location is not writable:" + configURL));
// return;
// }
// String locationString = configURL.getFile();
// File configDir = new File(locationString);
// if (!configDir.isDirectory())
// configDir = configDir.getParentFile();
// if (!configDir.exists()) {
// status.add(
// createStatus(null, "Configuration location does not exist"));
// return;
// }
// File locksDir = new File(configDir, "locks");
// // check all the possible lock files
// File[] lockFiles = locksDir.listFiles();
// File configLock = BootLoader.getCurrentPlatformConfiguration().getLockFile();
// for (int i = 0; i < lockFiles.length; i++) {
// if (lockFiles[i].equals(configLock))
// continue;
// try {
// RandomAccessFile raf = new RandomAccessFile(lockFiles[i], "rw");
// FileChannel channel = raf.getChannel();
// System.out.println(channel.isOpen());
// FileLock lock = channel.tryLock();
// if (lock == null){
// // there is another eclipse instance running
// raf.close();
// status.add(
// createStatus(
// null,
// "Another instance is running, please close it before performing any configuration operations"));
// return;
// }
//
// } catch (Exception e) {
// status.add(createStatus(null, "Failed to create lock:"+lockFiles[i]));
// return;
// }
// }
// }

    private static boolean isParent(
        IFeature candidate,
        IFeature feature,
        boolean optionalOnly)
        throws CoreException {
        IIncludedFeatureReference[] refs =
            candidate.getIncludedFeatureReferences();
        for (int i = 0; i < refs.length; i++) {
            IIncludedFeatureReference child = refs[i];
            VersionedIdentifier fvid = feature.getVersionedIdentifier();
            VersionedIdentifier cvid = child.getVersionedIdentifier();

            if (fvid.getIdentifier().equals(cvid.getIdentifier()) == false)
                continue;
            // same ID
 PluginVersionIdentifier fversion = fvid.getVersion();
            PluginVersionIdentifier cversion = cvid.getVersion();

            if (fversion.equals(cversion)) {
                // included and matched; return true if optionality is not
 // important or it is and the inclusion is optional
 return optionalOnly == false || child.isOptional();
            }
        }
        return false;
    }

// private static boolean checkTimeline(
// IInstallConfiguration config,
// ArrayList status) {
// try {
// ILocalSite lsite = SiteManager.getLocalSite();
// IInstallConfiguration cconfig = lsite.getCurrentConfiguration();
// if (cconfig.getTimeline() != config.getTimeline()) {
// // Not the same timeline - cannot revert
// String msg =
// UpdateUtils.getFormattedMessage(
// KEY_WRONG_TIMELINE,
// config.getLabel());
// status.add(createStatus(null, FeatureStatus.CODE_OTHER, msg));
// return false;
// }
// } catch (CoreException e) {
// status.add(e.getStatus());
// }
// return true;
// }

    private static IStatus createMultiStatus(
        String message,
        ArrayList children,
        int code) {
        IStatus[] carray =
            (IStatus[]) children.toArray(new IStatus[children.size()]);
        return new MultiStatus(
            UpdateCore.getPlugin().getBundle().getSymbolicName(),
            code,
            carray,
            message,
            null);
    }

    private static IStatus createStatus(IFeature feature, int errorCode, String message) {

        String fullMessage;
        if (feature == null)
            fullMessage = message;
        else {
            PluginVersionIdentifier version =
                feature.getVersionedIdentifier().getVersion();
            fullMessage =
                NLS.bind(Messages.ActivityConstraints_childMessage, (new String [] {
                feature.getLabel(),
                version.toString(),
                message }));
        }

        return new FeatureStatus(
            feature,
            IStatus.ERROR,
            UpdateCore.getPlugin().getBundle().getSymbolicName(),
            errorCode,
            fullMessage,
            null);
    }

    // private static IStatus createReportStatus(ArrayList beforeStatus,
 // ArrayList status) {
 // // report status
 // if (status.size() > 0) {
 // if (beforeStatus.size() > 0)
 // return createMultiStatus(KEY_ROOT_MESSAGE_INIT,
 // beforeStatus,IStatus.ERROR);
 // else
 // return createMultiStatus(KEY_ROOT_MESSAGE, status,IStatus.ERROR);
 // }
 // return null;
 // }

    private static IStatus createCombinedReportStatus(
        ArrayList beforeStatus,
        ArrayList status) {
        if (beforeStatus.size() == 0) { // good initial config
 if (status.size() == 0) {
                return null; // all fine
 } else {
                return createMultiStatus(Messages.ActivityConstraints_rootMessage,
                    status,
                    IStatus.ERROR);
                // error after operation
 }
        } else { // beforeStatus.size() > 0 : initial config errors
 if (status.size() == 0) {
                return null; // errors will be fixed
 } else {
                if (isBetterStatus(beforeStatus, status)) {
                    return createMultiStatus(
                        Messages.ActivityConstraints_warning,
                        beforeStatus,
                        IStatus.WARNING);
                    // errors may be fixed
 } else {
                    ArrayList combined = new ArrayList ();
                    combined.add(
                        createMultiStatus(
                            Messages.ActivityConstraints_beforeMessage,
                            beforeStatus,
                            IStatus.ERROR));
                    combined.add(
                        createMultiStatus(
                            Messages.ActivityConstraints_afterMessage,
                            status,
                            IStatus.ERROR));
                    return createMultiStatus(
                        Messages.ActivityConstraints_rootMessageInitial,
                        combined,
                        IStatus.ERROR);
                }
            }
        }
    }

    private static ArrayList createList(String commaSeparatedList) {
        ArrayList list = new ArrayList ();
        if (commaSeparatedList != null) {
            StringTokenizer t =
                new StringTokenizer (commaSeparatedList.trim(), ","); //$NON-NLS-1$
 while (t.hasMoreTokens()) {
                String token = t.nextToken().trim();
                if (!token.equals("")) //$NON-NLS-1$
 list.add(token);
            }
        }
        return list;
    }

    /**
     * Returns true if status is a subset of beforeStatus
     *
     * @param beforeStatus
     * @param status
     * @return
     */
    private static boolean isBetterStatus(
        ArrayList beforeStatus,
        ArrayList status) {
        // if no status at all, then it's a subset
 if (status == null || status.size() == 0)
            return true;
        // there is some status, so if there is no initial status, then it's
 // not a subset
 if (beforeStatus == null || beforeStatus.size() == 0)
            return false;
        // quick check
 if (beforeStatus.size() < status.size())
            return false;

        // check if all the status elements appear in the original status
 for (int i = 0; i < status.size(); i++) {
            IStatus s = (IStatus) status.get(i);
            // if this is not a feature status, something is wrong, so return
 // false
 if (!(s instanceof FeatureStatus))
                return false;
            FeatureStatus fs = (FeatureStatus) s;
            // check against all status elements
 boolean found = false;
            for (int j = 0; !found && j < beforeStatus.size(); j++) {
                if (fs.equals(beforeStatus.get(j)))
                    found = true;
            }
            if (!found)
                return false;
        }
        return true;
    }
    
    public class RequiredFeaturesResult {
        
        private IStatus status;
        private Set requiredFeatures;
        
        public Set getRequiredFeatures() {
            return requiredFeatures;
        }
        public void setRequiredFeatures(Set requiredFeatures) {
            this.requiredFeatures = requiredFeatures;
        }
        public void addRequiredFeatures(Set requiredFeatures) {
            if (requiredFeatures == null) {
                requiredFeatures = new HashSet ();
            }
            this.requiredFeatures.addAll(requiredFeatures);
        }
        public IStatus getStatus() {
            return status;
        }
        public void setStatus(IStatus status) {
            this.status = status;
        }
        
        
    }
    
    public static class InternalImport {
        
        private IImport iimport;

        public InternalImport(IImport iimport) {
            this.iimport = iimport;
        }
        
        public IImport getImport() {
            return iimport;
        }

        public void setImport(IImport iimport) {
            this.iimport = iimport;
        }
        
        public boolean equals(Object object) {

            if ( ( object == null) || !(object instanceof InternalImport))
                return false;
            
            if ( object == this)
                return true;
            
            return iimport.getVersionedIdentifier().equals( ((InternalImport)object).getImport().getVersionedIdentifier()) && (getImport().getRule() == ((InternalImport)object).getImport().getRule());

        }

        public int hashCode() {
            return iimport.getVersionedIdentifier().hashCode() * iimport.getRule();
        }
        
    }

}

